#pragma once

#include <functional>
#include <string>
#include <vector>
#include <cerrno>
#include <iostream>
#include <unordered_map>
#include "Sock.hpp"
#include "Util.hpp"
#include "EpollUtil.hpp"
#include "Protocol.hpp"

// 基于 Reactor 模式, 编写一个充分读取和写入的, EPOLL(ET) 的服务器

class TcpServer;
class Connection;
class Protocol;

using namespace std;

using fcn_t = function<void(Connection*)>;
using resolve_t = function<int(Connection*, string&)>;


class Connection
{
public:
    Connection(int sock, TcpServer* s = nullptr)
        : msock(sock)
        , msvr(s)
    {}

    void setRecver(fcn_t r)
    {
        mrecver = r;
    }

    void setSender(fcn_t s)
    {
        msender = s;
    }

    void setExcepter(fcn_t e)
    {
        mexcepter = e;
    }

    ~Connection()
    {}
public:
    TcpServer* msvr;                    // 服务器, 方便调用里面的函数
    int msock;                          // 文件描述符/套接字
    string mbuffer;                     // 输入缓冲区
    string mrbuffer;                    // 输出缓冲区   

    fcn_t mrecver;                      // 接收事件的回调函数
    fcn_t msender;                      // 发送事件的回调函数
    fcn_t mexcepter;                    // 异常事件的回调函数
};


class TcpServer
{
public:
    const static int readySize;
    TcpServer(resolve_t r, int port = 44444)
        : mlisSock(-1)
        , mepfd(-1)
        , mconnMapper()
        , mreadyEvents(nullptr)
        , mresolver(r)
    {
        cout << "服务器开始启动" << endl;
        // 开辟空间
        mreadyEvents = new struct epoll_event[readySize];               // 设置为非阻塞
        // 先绑定, 构建服务器的基本信息
        mlisSock = Sock::GetSock();
        Util::SetNonBlock(mlisSock);                                    // 设置成非阻塞
        // 绑定
        Sock::Bind(mlisSock, port);
        // 开始监听
        Sock::Listen(mlisSock);

        mepfd = EpollUtil::create();                                    // 创建 epoll 模型
        EpollUtil::addEvent(mepfd, mlisSock, EPOLLIN | EPOLLET);        // 为监听文件描述符创建事件, 需要关注读事件, 并且调整为 ET 模式
        
        Connection* cur = new Connection(mlisSock, this);               // 设置 监听文件描述符 的读事件
        cout << "监听sock: " << mlisSock << endl;
        cur->setRecver(std::bind(&TcpServer::accept, this, placeholders::_1));  // 为监听套接字提供 读事件 的回调函数
        mconnMapper.emplace(mlisSock, cur);
        cout << "服务器启动完成" << endl;
    }

    // 完成单次任务的分配
    void assign()
    {
        int n = EpollUtil::getReadyEvents(mepfd, mreadyEvents, readySize);      // 会返回实际上获取的就绪事件的个数
        // 开始派发/处理这些事件
        for (int i = 0; i < n; i ++)
        {
            int fd = mreadyEvents[i].data.fd;                                   // 相应事件的文件描述符
            uint32_t revent = mreadyEvents[i].events;                           // 对应的事件
            Connection* curconn = nullptr;                                      // 该事件对应的连接
            if (mconnMapper.count(fd) > 0) 
                curconn = mconnMapper[fd];

            /* 当发生异常的时候, 让这个文件描述符进行读取, 那么一定会出错, 可以将这个异常转移给 recver 进行处理, 将事件进行统一管理*/
            if (revent & EPOLLHUP || revent & EPOLLERR)                         // 连接关闭
            /* 设置读事件或者写事件就绪, 设置好之后就会进入读写事件的处理, 就可以不在 assign 函数内处理异常, 将异常转接给其他函数中执行 */
                revent |= (EPOLLIN | EPOLLOUT);                                 

            if (revent & EPOLLOUT)                                         // 如果有读事件
            {
                cout << "[" << fd << "] 文件有新事件 —— 写" << endl;
                if (curconn && curconn->msender)                                // 读事件回调
                    curconn->msender(curconn);
            }
            if (revent & EPOLLIN)                                               // 如果有写事件
            {
                cout << "[" << fd << "] 文件有新事件 —— 读" << endl;
                if (curconn && curconn->mrecver)                                // 写事件回调
                    curconn->mrecver(curconn);
            }
        }
    }

    /*
    获取连接, 当前监听连接 “接客” 获取新连接, 并加入到 epoll 中进行监听并且加入到 connection mapper 中进行管理
    ET 模式：有可能只来了一个连接, 但是也有可能一次来很多个连接, 所以就需要循环处理, 直到处理完本轮所有的数据
    */
    void accept(Connection* conn)                                               
    {
        while (true)
        {
            string cltIp ;
            uint16_t cltPort = 0;
            cout << "重复：监听sock: " << conn->msock << endl;
            int fd = Sock::Accept(conn->msock, &cltIp, &cltPort);
            if (fd < 0)
            {
                // 被信息中断，导致这个系统调用没有执行完成
                if (errno == EINTR) 
                {
                    cout << "信号中断, 重新监听" << endl;
                    continue;
                }     
                // 两个都表示底层没有数据了
                else if (errno == EAGAIN || errno == EWOULDBLOCK) 
                {  
                    cout << "连接全部获取完成" << endl;
                    break;                                                          // 如果错误码被设置, 那么表示连接获取完了
                }
                else 
                {
                    cerr << "Error: 获取新连接失败" << endl;
                    return ;
                }
            }
            cout << "成功获取新连接，fd = " << fd << endl;

            // 成功获取新连接, 将相关数据添加到 connection mapper 和 epoll 中
            addConnection(fd, EPOLLIN | EPOLLET, 
                bind(&TcpServer::serverRecver, this, placeholders::_1), 
                bind(&TcpServer::serverSender, this, placeholders::_1), 
                bind(&TcpServer::serverExcepter, this, placeholders::_1));
        }
    }

    // 添加新的连接, 构建一个新的连接对象, 然后加入相关数据结构中让服务器维护
    void addConnection(int sock, uint32_t event, fcn_t recver, fcn_t sender, fcn_t excepter)
    {
        if (event & EPOLLET)                                                                // 如果这个文件描述符的事件 是 ET 模式, 那么就设置成非阻塞
        {
            Util::SetNonBlock(sock);                                                        // 文件描述符设置为非阻塞式读取
        }
        // 将这个要监听的文件描述符和其事件添加到 epoll 模型中
        EpollUtil::addEvent(mepfd, sock, event);
        // 再添加到 connection mapper 中

        // 同时 这份新连接也需要设置  回调  函数
        Connection* cur = new Connection(sock, this);
        cur->setRecver(recver);                 // 绑定类内函数取药取地址
        cur->setSender(sender);
        cur->setExcepter(excepter);
        // cur->setRecver(bind(&TcpServer::setRecver, this, placeholders::_1));                 // 绑定类内函数取药取地址
        // cur->setSender(bind(&TcpServer::setSender, this, placeholders::_1));
        // cur->setExcepter(bind(&TcpServer::setExcepter, this, placeholders::_1));

        mconnMapper.emplace(sock, cur);
    }

    // 设置新连接的读取回调函数
    void serverRecver(Connection* conn)
    {
        cout << "[" << conn->msock << "] 文件发生读事件" << endl;
        while (true)
        {
            char buffer[1024] = {0};
            ssize_t rd = recv(conn->msock, buffer, sizeof(buffer) - 1, 0);
            if (rd > 0)                                                                             // 读取到数据
            {
                buffer[rd] = 0;
                conn->mbuffer += buffer;
            }
            else if (rd == 0)
            {
                conn->mexcepter(conn);                                                              // 处理异常
                cout << "客户端读取结束" << endl;
            }
            else                                                                                    // 否则
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)                                        // 如果错误码是这两个, 说明没有数据了
                    break;
                else if (errno == EINTR)                                                            // 因为被信号通知而中断了
                    continue;                                                                       // 说明还没读完
                else                                                                                // 出错
                {
                    // Error
                    cout << "Debug: 事件读取出错" << endl;
                    conn->mexcepter(conn);
                    break;
                }
            }
        }
        // 数据读取完成, 分割数据
        vector<string> result;
        DataSplit(conn->mbuffer, result);                                                 // 将数据分割成一个个数据报
        cout << "[" << conn->msock << "] 拆分数据报完成" << endl;
        // 分割完成之后根据业务处理数据, 再将数据返回给对方
        for (auto& data : result)
        {
            cout << "Debug: 数据为 [" << data << "]" << endl;
            mresolver(conn, data);                                                                  // 解析这些数据
        }
    }

    void serverSender(Connection* conn)
    {
        cout << "Debug: 执行 发送 方法" << endl;
        // ET 模式：同样需要不断发送数据
        cout << "Debug: 输出缓冲区中的数据 [" << conn->mrbuffer << "]" << endl;
        while (true)
        {
            ssize_t wr = write(conn->msock, conn->mrbuffer.c_str(), conn->mrbuffer.size());
            // 发送成功, 本质上就是发送给操作系统缓冲区
            if (wr > 0)                                                                             // 实际上发送成功的字节数
            {
                conn->mrbuffer.erase(0, wr);                                                        // 移出缓冲区
            }
            else 
            {
                if (errno == EINTR)     continue;                                                   // 信号导致的异常, 那么就继续执行
                // 说明发送完成, 但是 mrbuffer 不一定为空, 可能是将内核缓冲区写满了
                // 如果 mrbuffer 没发送完, 那么 EPOLLOUT 应该继续保持开着的状态
                // 否则就应该关闭 EPOLLOUT
                else if (errno == EAGAIN || errno == EWOULDBLOCK)                                   // 表示数据写满了                            
                    break;
                else 
                {
                    conn->mexcepter(conn);
                    cout << "发生异常" << endl;
                    break;
                }
            }
        }
    }

    // 处理服务器异常
    void serverExcepter(Connection* conn)
    {
        if (!mconnMapper.count(conn->msock))
            return;
        
        // 一定要先从 epoll 中移除, 再关闭文件描述符
        EpollUtil::deleteEvent(mepfd, conn->msock);
        close(conn->msock);
        delete mconnMapper[conn->msock];                                                                // 释放这个连接对象
        mconnMapper.erase(conn->msock);
    }

    // epoll 中对读事件或者写事件进行关心或者关闭
    void readWriteCtl(int fd, bool readable, bool writable)
    {
        uint32_t tochange = 0;                                                                      // 需要关心的事件
        tochange |= (readable ? EPOLLIN : 0);
        tochange |= (writable ? EPOLLOUT : 0);
        EpollUtil::modifyEvent(mepfd, fd, tochange);
    }

    void run()
    {
        // 启动之后, 只需要不断循环  找到触发事件并且执行就好了
        while (true)
        {
            assign();   // 分配任务
        }
    }

    ~TcpServer()
    {
        if (mlisSock >= 0)
            close(mlisSock);
        if (mepfd >= 0)
            close(mepfd);

        delete[] mreadyEvents;
    }
private:
    int mlisSock;                                                                       // 监听套接字
    int mepfd;                                                                          // epoll 模型文件描述符
    unordered_map<int, Connection*> mconnMapper;                                        // 文件描述符 以及其 对应的连接  建立键值对关系
    struct epoll_event* mreadyEvents;                                                   // 就绪事件数组
    resolve_t mresolver;                                                                // 处理数据的函数
};
const int TcpServer::readySize = 128;                                                   // 一次读取 128 个就绪事件

// 可以保证, msg 是一个完整的报文, 因为数据已经被处理成了一个个包了
int ResolveData(Connection* conn, string& msg)                                          // 处理这些数据
{
    cout << "[" << conn->msock << "] 开始处理数据" << endl;
    // 1. 获取一条条数据报：至此就是一条一条报文了，接下来需要对这些数据进行反序列化成原生数据

    // 2. 业务处理：再对这些数据做处理

    cout << "获取数据 data = [" << msg << "]" << endl;
    // 3. 序列化：处理完成数据之后, 再进行序列化

    // 4. 发送回去：将序列化之后的结果发送回给对方
    string msgToSend = msg; // 要发送的数据
    conn->mrbuffer += msgToSend;
    // 5. 直接发送一次
    conn->msender(conn);

    cout << "数据处理完成, 并且已经放入到 Connection 缓冲区中" << endl;
    cout << "Debug: 发送数据[" << conn->mrbuffer << "]" << endl;

    // 5. 多路转接的发送问题
    if (conn->mrbuffer.empty())
        conn->msvr->readWriteCtl(conn->msock, true, false);
    else 
        conn->msvr->readWriteCtl(conn->msock, true, true);
    cout << "Debug: 设置文件描述符为 EPOLLOUT, 数据即将发送";
    return 0;
}